﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Netbits.Common.Extensions;
using Netbits.Common.General;

namespace Netbits.Common.Threading
{
	/// <summary>
	/// Represents a Extended-Managed-Thread 
	/// </summary>
	public sealed class ThreadEntry : IDisposable
	{
		#region Static constance

		/// <summary>
		/// Defines that the thread was successfully initializes.
		/// </summary>
		public const int THREAD_INITIALIZED	= 0;
		/// <summary>
		/// Defines that the thread is now starting
		/// </summary>
		public const int THREAD_STARTING = 1;
		/// <summary>
		/// Defines that the thread is started successfully
		/// </summary>
		public const int THREAD_STARTED = 2;
		/// <summary>
		/// Defines that the thread is now paused.
		/// </summary>
		public const int THREAD_PAUSED = 3;
		/// <summary>
		/// Defines that the thread is now stopping
		/// </summary>
		public const int THREAD_STOPPING = 4;
		/// <summary>
		/// Defines that the thread is stopped successfully
		/// </summary>
		public const int THREAD_STOPPPED = 5;
		/// <summary>
		/// Defines that the thread stops working and has an error.
		/// </summary>
		public const int THREAD_ERROR = 6;
		/// <summary>
		/// Ticks for one second.
		/// </summary>
		static long TICKS_FOR_ONE_SECOND = TimeSpan.FromSeconds( 1 ).Ticks;

		#endregion

		#region Private Variables

		/// <summary>
		/// Maximum time to wait for stopping the thread.
		/// </summary>
		TimeSpan _StopTimeout;
		/// <summary>
		/// Sleep-Timeout 
		/// </summary>
		TimeSpan _SleepTimeout;
		/// <summary>
		/// Interval for sleep-cycle
		/// </summary>
		TimeSpan _SleepTimeoutInterval;
		/// <summary>
		/// The managed thread-id
		/// </summary>
		Thread _Thread;
		/// <summary>
		/// Pause-mode handling
		/// </summary>
		ManualResetEventSlim _HandlePause;
		/// <summary>
		/// Lock instance for multi-threading
		/// </summary>
		Lock _Lock;
		/// <summary>
		/// Next-Step handler for external thread-execution controlling
		/// </summary>
		ManualResetEventSlim _HandleNextStep = null;
		/// <summary>
		/// Action for thread-step execute
		/// </summary>
		Action<ThreadEntry, object[]> _Run = null;
		/// <summary>
		/// Parameters for the thread-execute
		/// </summary>
		object[] _Parameters;
		/// <summary>
		/// Must the thread stop?
		/// </summary>
		int _StopThread = 0;
		/// <summary>
		/// Operation State
		/// </summary>
		int _OperationState;
		/// <summary>
		/// Save the last error.
		/// </summary>
		Exception _LastError;
		/// <summary>
		/// Set if the thread was already stopped. Can't started once again
		/// </summary>
		int _WasThreadStopped = 0;
		/// <summary>
		/// On every thread-cycle the counter was incremented.
		/// </summary>
		long _CycleCounter;
		/// <summary>
		/// The actually CPU-usage 
		/// </summary>
		int _CpuUsage;

		bool _Disposed;
#if LINUX
		int _LinuxId = -1;
		string _StatFile = string.Empty;
		byte[] _ReadBuffer = new byte[4096];
		long _LastCPUTime = 0;
		long _LastStartTime = DateTime.MinValue.Ticks;
#elif WINDOWS
		IntPtr _ThreadHandle;
		long _LastCPUTime = 0;
		long _LastStartTime = DateTime.MinValue.Ticks;
#endif
		#endregion

		#region Constructor

		ThreadEntry( bool SetThreadName_ )
		{
			_Thread = new Thread( new ThreadStart( _ThreadHandler ) );
			if( SetThreadName_ )
				_Thread.Name = string.Format( "thread-{0}", _Thread.ManagedThreadId );

			_HandlePause = new ManualResetEventSlim( true );
			_Lock = new Lock();
			_OperationState = THREAD_INITIALIZED;
			_StopTimeout = new TimeSpan( 0, 0, 5 );
			_SleepTimeout = TimeSpan.MinValue;
			_SleepTimeoutInterval = new TimeSpan( 0, 0, 0, 0, 100 );
		}

		/// <summary>
		/// Destructor dispose unmanaged source
		/// </summary>
		~ThreadEntry()
		{
			Dispose( false );
		}

		internal ThreadEntry() : this( true )
		{
		}

		internal ThreadEntry( string Name_ ) : this( string.IsNullOrEmpty( Name_ ) )
		{
			if( !string.IsNullOrEmpty( Name_ ) )
				_Thread.Name = Name_;
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Get or set the name of the thread.
		/// </summary>
		public string Name
		{
			get
			{
				return _Thread.Name;
			}
			set
			{
				_Thread.Name = value;
			}
		}

		/// <summary>
		/// Returns the managed-thread ids
		/// </summary>
		public int Id
		{
			get
			{
				return _Thread.ManagedThreadId;
			}
		}

#if LINUX
		/// <summary>
		/// Get the Linux-thread id
		/// </summary>
		public int LinuxId
		{
			get
			{
				return _LinuxId;
			}
		}
#endif

		/// <summary>
		/// Gets a value indicating the execution status of the current thread.
		/// </summary>
		public bool IsAlive
		{
			get
			{
				return _Thread.IsAlive;
			}
		}

		/// <summary>
		/// Gets an ExecutionContext object that contains information about the various contexts of the current thread.
		/// </summary>
		public ExecutionContext Context
		{
			get
			{
				return _Thread.ExecutionContext;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether or not a thread is a background thread.
		/// </summary>
		public bool IsBackground
		{
			get
			{
				return _Thread.IsBackground;
			}
			set
			{
				_Thread.IsBackground = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating the scheduling priority of a thread.
		/// </summary>
		public ThreadPriority Priority
		{
			get
			{
				return _Thread.Priority;
			}
			set
			{
				_Thread.Priority = value;
			}
		}

		/// <summary>
		/// Action that was executed from the thread.
		/// </summary>
		public Action<ThreadEntry, object[]> Run
		{
			get
			{
				try
				{
					_Lock.EnterReadLock();
					return _Run;
				}
				finally
				{
					_Lock.ExitReadLock();
				}
			}
			set
			{
				_Run = value;
			}
		}

		/// <summary>
		/// Get or sets the sleep-timeout that was execute after run
		/// TimeSpan.MinValue disable sleeping
		/// </summary>
		public TimeSpan SleepAfterRun
		{
			get
			{
				try
				{
					_Lock.EnterReadLock();
					return _SleepTimeout;
				}
				finally
				{
					_Lock.ExitReadLock();
				}
			}
			set
			{
				try
				{
					_Lock.EnterWriteLock();
					_SleepTimeout = value;
				}
				finally
				{
					_Lock.ExitWriteLock();
				}
			}
		}

		/// <summary>
		/// Get or sets the stop-timeout
		/// If the thread can't stop normally (join) the timeout is wait before the thread was abort.
		/// </summary>
		public TimeSpan StopTimeout
		{
			get
			{
				try
				{
					_Lock.EnterReadLock();
					return _StopTimeout;
				}
				finally
				{
					_Lock.ExitReadLock();
				}
			}
			set
			{
				try
				{
					_Lock.EnterWriteLock();
					_StopTimeout = value;
				}
				finally
				{
					_Lock.ExitWriteLock();
				}
			}
		}

		/// <summary>
		/// Represents the thread operation state
		/// Constants 
		///		THREAD_INITIALIZED
		///		THREAD_STARTING
		///		THREAD_STARTED
		///		THREAD_PAUSED
		///		THREAD_STOPPING
		///		THREAD_STOPPPED
		/// </summary>
		public int OperationState
		{
			get
			{
				return _OperationState;
			}
			internal set
			{
				Interlocked.Exchange( ref _OperationState, value );
			}
		}

		/// <summary>
		/// Get the actually thread-cycle-counter
		/// </summary>
		public long CycleCounter
		{
			get
			{
				return _CycleCounter;
			}
		}

		/// <summary>
		/// Get or sets the handle-next-step ManualResetEventSlim
		/// </summary>
		public ManualResetEventSlim HandleNextStep
		{
			get
			{
				try
				{
					_Lock.EnterReadLock();
					return _HandleNextStep;
				}
				finally
				{
					_Lock.ExitReadLock();
				}
			}
			set
			{
				try
				{
					_Lock.EnterWriteLock();
					_HandleNextStep = value;
				}
				finally
				{
					_Lock.ExitWriteLock();
				}
			}
		}

		/// <summary>
		/// Get the actually CPU-usage in percent 
		/// </summary>
		public int CpuUsage
		{
			get
			{
				return _CpuUsage;
			}
		}

		#endregion

		#region Public Static Functions

		/// <summary>
		/// Blocks the current thread for a specified time.
		/// </summary>
		/// <param name="Timeout_">The amount of time for which the thread is blocked</param>
		public static void Sleep( TimeSpan Timeout_ )
		{
			System.Threading.Thread.Sleep( Timeout_ );
		}

		/// <summary>
		/// Blocks the current thread for a specified time.
		/// </summary>
		/// <param name="Timeout_">The amount of time for which the thread is blocked</param>
		public static void Sleep( int Timeout_ )
		{
			System.Threading.Thread.Sleep( Timeout_ );
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Starting the thread
		/// </summary>
		public void Start()
		{
			if( _OperationState == THREAD_PAUSED )
			{
				_HandlePause.Set();
				OperationState = THREAD_STARTED;
				return;
			}

			if( _OperationState != THREAD_INITIALIZED && _OperationState != THREAD_STOPPPED && _OperationState != THREAD_ERROR )
				throw new ThreadStateException( "Can't start thread. thread is running..." );

			if( _WasThreadStopped > 0 )
				throw new ThreadStateException( "You can't start the thread once again" );

			_LastError = null;
			_Thread.Start();
		}

		/// <summary>
		/// Starting the thread with the specified parameters
		/// </summary>
		/// <param name="Parameters_">The specified parameters for thread execute</param>
		public void Start( object[] Parameters_ )
		{
			try
			{
				_Lock.EnterWriteLock();
				_Parameters = Parameters_;
			}
			finally
			{
				_Lock.ExitWriteLock();
			}

			Start();
		}

		/// <summary>
		/// Stopping the thread
		/// </summary>
		public void Stop()
		{
			if( _OperationState == THREAD_STOPPPED || _OperationState == THREAD_STOPPING )
				return;

			if( _OperationState != THREAD_STARTED && _OperationState != THREAD_PAUSED && _OperationState != THREAD_ERROR )
				throw new ThreadStateException( string.Format( "Can't stop thread. Current thread state is {0}", _OperationState ) );

			int lastState = _OperationState;

			Interlocked.Exchange( ref _OperationState, THREAD_STOPPING );
			Interlocked.Exchange( ref _StopThread, 1 );

			if( lastState == THREAD_ERROR )
			{
				Interlocked.Exchange( ref _OperationState, THREAD_STOPPPED );
				return;
			}

			Interlocked.Exchange( ref _WasThreadStopped, 1 );

			if( Thread.CurrentThread.ManagedThreadId != _Thread.ManagedThreadId )
			{
				Console.WriteLine( "check if thread is paused so interrupted" );
				if( lastState == THREAD_PAUSED )
				{
					Console.WriteLine( "interrupt thread" );
					_Thread.Interrupt();
				}

				Console.WriteLine( "check if thread is alive" );
				if( _Thread.IsAlive )
				{
					Console.WriteLine( "join with timeout" );
					if( !_Thread.Join( _StopTimeout ) )
					{
						Console.WriteLine( "abort thread" );
						_Thread.Abort();
					}
				}
			}
			else
			{
				Console.WriteLine( "same thread! abort this" );
				_Thread.Abort();
			}
			Interlocked.Exchange( ref _OperationState, THREAD_STOPPPED );
		}

		/// <summary>
		/// Notify the thread that must pause on next step.
		/// </summary>
		public void Pause()
		{
			// check if the thread is in pause-mode
			if( _OperationState == THREAD_PAUSED )
				return;

			OperationState = THREAD_PAUSED;
			_HandlePause.Reset();
		}

		/// <summary>
		/// Increment the cycle counter and recalculate the CPU-usage
		/// </summary>
		public void IncrementCycleCounterAndSetCpuUsage()
		{
			_AnalyseCpuUsage();
			_IncrementCycleCounter();
		}

		#endregion

		#region Private Functions

		void Dispose( bool Disposing_ )
		{
			if( !_Disposed )
			{
				if( Disposing_ )
				{
					Stop();
					ThreadManager.Remove( this );
					_Thread = null;

					if( _HandlePause != null )
					{
						_HandlePause.Dispose();
						_HandlePause = null;
					}

					if( _HandleNextStep != null )
					{
						_HandleNextStep.Dispose();
						_HandleNextStep = null;
					}

					if( _Lock != null )
					{
						_Lock.Dispose();
						_Lock = null;
					}
				}

				_ThreadHandle = IntPtr.Zero;
				_Disposed = true;
			}

		}


#if LINUX
		[DllImport( "libc" )]
		static extern int prctl( int Option_, byte[] Argument2_, IntPtr Argument3_, IntPtr Argument4_, IntPtr Argument5_ );
#endif

		void _ThreadHandler()
		{
#if LINUX
			try
			{
				string name = string.Format( "MT:{0}\0", _Thread.ManagedThreadId );
				prctl( 15, System.Text.Encoding.ASCII.GetBytes( name ), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero );
			}
			finally
			{
			}
#endif 

			try
			{
				// Determine the static / once thread-informations
				_AnalyseStaticThreadInformation();
				OperationState = THREAD_STARTED;

				while( _StopThread == 0 )
				{
					// Start the CPU-usage analyses
					//_StartAnalyseCpuUsage();
					_AnalyseCpuUsage();

					// Check if the thread is in pause-mode.
					if( !_HandlePause.IsSet )
					{
						while( !_HandlePause.Wait( _SleepTimeoutInterval ) )
							IncrementCycleCounterAndSetCpuUsage();

						OperationState = THREAD_STARTED;
					}

					// Get the actually parameters for execution
					object[] parameters = null;
					TimeSpan sleepTime = TimeSpan.MinValue;
					ManualResetEventSlim handleNextStep;
					try
					{
						_Lock.EnterReadLock();
						parameters = _Parameters;
						sleepTime = _SleepTimeout;
						handleNextStep = _HandleNextStep;
					}
					finally
					{
						_Lock.ExitReadLock();
					}

					// Check if the thread must wait for the next step.
					if( handleNextStep != null )
					{
						while( !handleNextStep.Wait( _SleepTimeoutInterval ) )
							IncrementCycleCounterAndSetCpuUsage();
					}

					// Run thread-action with parameters.
					_Run( this, parameters );

					// Check if we must sleep after run.
					if( sleepTime != TimeSpan.MinValue )
					{
						long count = 0;
						if( sleepTime.Ticks > _SleepTimeoutInterval.Ticks )
							count = sleepTime.Ticks / _SleepTimeoutInterval.Ticks;
						else
							count = 1;

						for( long i = 0; i < count; i++ )
						{
							Sleep( _SleepTimeoutInterval );
							IncrementCycleCounterAndSetCpuUsage();
						}
					}

					IncrementCycleCounterAndSetCpuUsage();
				}

				Interlocked.Exchange( ref _OperationState, THREAD_STOPPPED );
			}
			catch( ThreadAbortException )
			{
			}
			catch( ThreadInterruptedException )
			{
			}
			catch( Exception Ex_ )
			{
				_LastError = Ex_;
				Interlocked.Exchange( ref _OperationState, THREAD_ERROR );
			}
		}

		void _IncrementCycleCounter()
		{
			if( _CycleCounter == long.MaxValue )
				Interlocked.Exchange( ref _CycleCounter, 0 );
			else
				Interlocked.Increment( ref _CycleCounter );
		}

#if WINDOWS 
		long _GetThreadUsageTimes()
		{
			long dummy;
			long utime;
			long stime;

			if( NativeMethods.GetThreadTimes( _ThreadHandle, out dummy, out dummy, out stime, out utime ) )
				return stime + utime;

			return -1;
		}
#elif LINUX
		long _GetThreadUsageTimes()
		{
			if( string.IsNullOrEmpty( _StatFile ) )
				_StatFile = string.Format( "/proc/{0}/task/{1}/stat", ThreadManager.ProcessId, _LinuxId );

			int readed = 0;
			using( System.IO.BinaryReader br = new System.IO.BinaryReader( System.IO.File.OpenRead( _StatFile ) ) )
			{
				readed = br.Read( _ReadBuffer, 0, _ReadBuffer.Length );
				br.Close();
			}

			string line = System.Text.Encoding.UTF8.GetString( _ReadBuffer, 0, readed );
			long newCpuTime = _ReadStatInfo( line, 13 ).ConvertTo<long>( 0 );
			newCpuTime += _ReadStatInfo( line, 14 ).ConvertTo<long>( 0 );
			newCpuTime += _ReadStatInfo( line, 15 ).ConvertTo<long>( 0 );
			newCpuTime += _ReadStatInfo( line, 16 ).ConvertTo<long>( 0 );
			newCpuTime *= 100000;

			return newCpuTime;
		}

		string _ReadStatInfo( string Data_, ushort Index_ )
		{
			StringBuilder sb = new StringBuilder();
			ushort index = 0;
			bool inString = false;

			foreach( char c in Data_ )
			{
				switch( c )
				{
					case ' ':
						if( inString )
						{
							sb.Append( c );
							continue;
						}

						if( index == Index_ )
							return sb.ToString();

						++index;
						sb.Clear();
						continue;
					case '(':
						inString = true;
						continue;
					case ')':
						inString = false;
						continue;
					default:
						sb.Append( c );
						continue;
				}
			}

			return string.Empty;
		}
#endif

		void _AnalyseCpuUsage()
		{
			long nowTicks = DateTime.Now.Ticks;

			if( _LastStartTime == DateTime.MinValue.Ticks )
			{
				_LastCPUTime = _GetThreadUsageTimes();
				_LastStartTime = nowTicks;
				return;
			}

			if( (nowTicks - _LastStartTime) < (TICKS_FOR_ONE_SECOND / 10) )
				return;

			long endCpuTime = _GetThreadUsageTimes();
			long diffCpuTime = endCpuTime - _LastCPUTime;
			long diffExecutionTime = nowTicks - _LastStartTime;

			int cpuusage = (int)((diffCpuTime * 100) / diffExecutionTime);
			if( cpuusage > 100 )
				cpuusage = 100;

			Interlocked.Exchange( ref _CpuUsage, cpuusage );

			if( nowTicks - _LastStartTime > TICKS_FOR_ONE_SECOND )
			{
				_LastStartTime = nowTicks;
				_LastCPUTime = endCpuTime;
			}
		}

		void _AnalyseStaticThreadInformation()
		{
#if LINUX
			string name = string.Format( "MT:{0}", _Thread.ManagedThreadId );
			string path = string.Format( "/proc/{0}/task", Process.GetCurrentProcess().Id );

			List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();
			System.IO.DirectoryInfo di = new System.IO.DirectoryInfo( path );
			foreach( System.IO.DirectoryInfo diTask in di.GetDirectories() )
				files.AddRange( diTask.GetFiles( "status" ) );

			path = string.Empty;
			byte[] buf = new byte[100];
			foreach( System.IO.FileInfo status in files )
			{
				if( !status.FullName.EndsWith( "status", StringComparison.InvariantCulture ) )
					continue;

				int readed;
				try
				{
					System.IO.FileStream fs = status.OpenRead();
					readed = fs.Read( buf, 0, 100 );
					fs.Close();
					fs.Dispose();
				}
				catch
				{
					return;
				}

				string text = System.Text.Encoding.ASCII.GetString( buf, 0, readed );
				if( text.Contains( name ) )
				{
					string directory = System.IO.Path.GetDirectoryName( status.FullName );
					_LinuxId = System.IO.Path.GetFileName( directory ).ConvertTo<int>( -1 );
					path = directory;
					break;
				}
			}
#elif WINDOWS
			_ThreadHandle = NativeMethods.GetCurrentThread();
#endif
		}

		#endregion

		#region Overrides

		/// <summary>
		/// Returns a hash code for the current thread
		/// </summary>
		/// <returns>The hash code</returns>
		public override int GetHashCode()
		{
			return _Thread.GetHashCode();
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Remove all resources.
		/// </summary>
		public void Dispose()
		{
			Dispose( true );
			GC.SuppressFinalize( this );
		}

		#endregion
	}
}
